package com.ihr360.commons.lang;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;

import javax.persistence.Entity;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author davidwei
 */
public class Ihr360BeanUtils {
    private static final Logger logger = LoggerFactory.getLogger(Ihr360BeanUtils.class);
    private static final String ID_NAME = "id";

    private Ihr360BeanUtils() {
    }

    public static String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<>();
        for (java.beans.PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }
        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }

    /**
     * 将Map中的数据拷贝到Bean中
     *
     * @param source
     * @param bean
     * @return
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    public static Object copyMapToBean(Map<String, Object> source, Object bean) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
        final BeanWrapper src = new BeanWrapperImpl(bean);
        for (Map.Entry<String, Object> entry : source.entrySet()) {
            String filedName = entry.getKey();
            Object fieldValue = entry.getValue();
            copyProperty(bean, src, filedName, fieldValue);
        }
        return bean;
    }

    private static void copyProperty(Object bean, BeanWrapper src, String filedName, Object fieldValue) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        Class<?> userClass = ClassUtils.getUserClass(src.getPropertyType(filedName));
        try {
            src.getPropertyDescriptor(filedName);
        } catch (InvalidPropertyException e) {
            if (logger.isInfoEnabled()) {
                logger.warn(e.getMessage(), e);
            }
            return;
        }

        if (!src.isReadableProperty(filedName)){
            return;
        }
        if (!src.isWritableProperty(filedName)){
            return;
        }

        if (fieldValue == null) {
            src.setPropertyValue(filedName, null);
        } else if (ClassUtils.isPrimitiveOrWrapper(userClass)) {
            Object targetFiledValue = fieldValue;
            org.apache.commons.beanutils.BeanUtils.copyProperty(bean, filedName, targetFiledValue);
        } else if (userClass.isEnum()) {
            copyEnum(bean, filedName, fieldValue, userClass);
        } else if (userClass.equals(Date.class)) {
            Date targetValue = DateUtils.parse(fieldValue.toString());
            org.apache.commons.beanutils.BeanUtils.copyProperty(bean, filedName, targetValue);
        } else if (fieldValue instanceof Iterable) {
            logger.info(userClass.toString());


            Object propertyValue = src.getPropertyValue(filedName);
            Class propertyType = src.getPropertyType(filedName);

            if (!(propertyType.isArray() || propertyType.isAssignableFrom(List.class))) {
                logger.warn("Property value type and fieldValue type is not math. Field Name {}", filedName);
                return;
            }

            List listFieldValue = (List) fieldValue;
            List listPropertyValue = (List) propertyValue;

            boolean isPrimaryListType = copyList(src, filedName, listFieldValue, listPropertyValue);

            if (isPrimaryListType) {
                org.apache.commons.beanutils.BeanUtils.copyProperty(bean, filedName, fieldValue);
            }

        } else {
            Object newFiledValue = null;

            newFiledValue = copyMap(src, filedName, fieldValue, userClass, newFiledValue);
            if (newFiledValue == null) {
                newFiledValue = fieldValue;
            }

            org.apache.commons.beanutils.BeanUtils.copyProperty(bean, filedName, newFiledValue);
        }
    }

    /**
     * @param src
     * @param listFieldValue
     * @param listPropertyValue
     * @return 如何List 为基本类型的list
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     */
    private static boolean copyList(Object bean, String filedName, List listFieldValue, List listPropertyValue) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {

        Map<Object, Object> idAndBeanMap = new HashMap<>();
        for (Object innerPropertyValue : listPropertyValue) {

            if (ClassUtils.isPrimitiveOrWrapper(innerPropertyValue.getClass())) {
                return true;
            }
            if (innerPropertyValue.getClass().getAnnotation(Entity.class) == null) {
                return true;
            }
            BeanWrapper beanWrapper = new BeanWrapperImpl(innerPropertyValue);
            try {
                beanWrapper.getPropertyDescriptor(ID_NAME);
            } catch (InvalidPropertyException e) {
                if (logger.isInfoEnabled()) {
                    logger.warn(e.getMessage(), e);
                }
                return true;
            }
            Object id = beanWrapper.getPropertyValue(ID_NAME);
            idAndBeanMap.put(id, innerPropertyValue);
        }
        for (Object innerValue : listFieldValue) {
            //FIX ME
            // 暂时只支持Entity的主键为单一id的情况
            if (innerValue instanceof Map) {
                Map<String, Object> map = (Map) innerValue;
                Object id = map.get(ID_NAME);
                if (id instanceof Integer) {
                    id = ((Integer) id).longValue();
                }
                Object innerBean = idAndBeanMap.get(id);
                //只支持对已有内容的更新,不支持在更新的同时添加关联数据
                if (innerBean == null) {
                    continue;
                }
                copyMapToBean(map, innerBean);
                idAndBeanMap.put(id, innerBean);
            } else {
                if (ClassUtils.isPrimitiveOrWrapper(innerValue.getClass())) {
                    return true;
                }
            }
        }
        org.apache.commons.beanutils.BeanUtils.copyProperty(bean, filedName, idAndBeanMap.values());
        return false;
    }

    private static Object copyMap(BeanWrapper src, String filedName, Object fieldValue, Class<?> userClass, Object newFiledValue) throws IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
        if (fieldValue instanceof Map) {
            Object propertyValue = src.getPropertyValue(filedName);
            //考虑带有Embedded的更新是否支持
            if (AnnotationUtils.findAnnotation(userClass, Entity.class) != null) {
                propertyValue = BeanUtils.instantiate(userClass);
            }
            if (propertyValue == null) {
                propertyValue = BeanUtils.instantiate(userClass);
            }
            newFiledValue = copyMapToBean((Map) fieldValue, propertyValue);
        }
        return newFiledValue;
    }

    private static void copyEnum(Object bean, String filedName, Object fieldValue, Class<?> userClass) throws IllegalAccessException, InvocationTargetException {
        Enum[] enums = (Enum[]) userClass.getEnumConstants();
        Enum targetValue = null;
        if(fieldValue instanceof Map) {
            fieldValue = ((Map) fieldValue).get("id");
        }
        for (Enum obj : enums) {
            if (obj.name().equals(fieldValue)) {
                targetValue = obj;
                break;
            }
        }
        org.apache.commons.beanutils.BeanUtils.copyProperty(bean, filedName, targetValue);
    }


}